﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using AutoLoL.Entities;
using Ionic.Zlib;

namespace AutoLoL.Logic.Parsers
{
    public class RafManager
    {
        private static RafManager _instance;
        public static RafManager Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new RafManager();
                }

                return _instance;
            }
        }

        public ReadOnlyCollection<RafArchive> Archives { get; private set; }

        private RafManager()
        {
            List<RafArchive> rafArchives = new List<RafArchive>();

            string archivesDirectory = Path.Combine(UserSettings.Instance.LolDirectory, "rads", "projects", "lol_game_client", "filearchives");

            // Read archives
            foreach (string archiveDirectory in Directory.GetDirectories(archivesDirectory))
            {
                string[] files = Directory.GetFiles(archiveDirectory, "*.raf");

                if (files.Length > 0)
                {
                    foreach (string rafPath in files)
                    {
                        if (File.Exists(rafPath + ".dat"))
                        {
                            RafArchive archive = GetArchive(rafPath);
                            rafArchives.Add(archive);
                        }
                    }
                }
            }

            rafArchives.Sort((x1, x2) => x1.Version.CompareTo(x2.Version));
            rafArchives.Reverse();

            this.Archives = new ReadOnlyCollection<RafArchive>(rafArchives);
        }

        public RafFile GetFile(string path)
        {
            path = path.ToLower();

            foreach (RafArchive archive in Archives)
            {
                RafFile file = archive.Files.Where(c => c.Path.ToLower().Equals(path)).SingleOrDefault();

                if (file != null)
                {
                    return file;
                }
            }

            return null;
        }

        public ReadOnlyCollection<RafFile> GetFiles(Func<string, bool> Filter)
        {
            Dictionary<string, RafFile> files = new Dictionary<string, RafFile>();

            foreach (RafArchive archive in Archives)
            {
                foreach (RafFile file in archive.Files)
                {
                    if (Filter(file.Path))
                    {
                        if (!files.ContainsKey(file.Path.ToLower()))
                        {
                            files.Add(file.Path.ToLower(), file);
                        }
                    }
                }
            }

            return new ReadOnlyCollection<RafFile>(files.Values.ToList());
        }

        /// <summary>
        /// Loads an archive file
        /// </summary>
        /// <param name="rafPath"></param>
        /// <returns></returns>
        private RafArchive GetArchive(string rafPath)
        {
            string datPath = rafPath + ".dat";
            byte[] data = File.ReadAllBytes(rafPath);
            string directory;

            string[] backwardSplit = rafPath.Split('\\');
            string[] forwardSplit = rafPath.Split('/');

            if (backwardSplit.Length > forwardSplit.Length)
            {
                directory = backwardSplit[backwardSplit.Length - 2];
            }
            else
            {
                directory = forwardSplit[backwardSplit.Length - 2];
            }

            Version version = new Version(directory);
            RafArchive archive = new RafArchive(version, rafPath, data);

            if (!File.Exists(rafPath) || !File.Exists(archive.DatPath))
            {
                return null;
            }

            // First we'll get all file paths
            UInt32 filePathsOffset = BitConverter.ToUInt32(SubArray<byte>(data, 16, 4), 0);
            UInt32 filePathsTotal = BitConverter.ToUInt32(data, (int)filePathsOffset + 4);

            string[] filePaths = new string[filePathsTotal];

            for (UInt32 i = 0; i < filePathsTotal; i++)
            {
                UInt32 entryOffset = filePathsOffset + 8 + i * 8;
                UInt32 entryValueOffset = BitConverter.ToUInt32(data, (int)entryOffset);
                UInt32 entryValueSize = BitConverter.ToUInt32(data, (int)entryOffset + 4);

                byte[] stringBytes = SubArray<byte>(data, (int)(entryValueOffset + filePathsOffset), (int)entryValueSize - 1);
                string filePath = Encoding.ASCII.GetString(stringBytes);
                filePaths[i] = filePath;
            }

            // Then we'll get all the file locations in the .dat file
            UInt32 fileInfosStart = BitConverter.ToUInt32(SubArray<byte>(data, 12, 4), 0);
            UInt32 fileInfosOffset = fileInfosStart + 4;
            UInt32 fileInfosTotal = BitConverter.ToUInt32(SubArray<byte>(data, (Int32)fileInfosStart, 4), 0);

            List<RafFile> files = new List<RafFile>();
            for (UInt32 offset = fileInfosOffset; offset < fileInfosOffset + 16 * fileInfosTotal; offset += 16)
            {
                UInt32 datFileOffset = BitConverter.ToUInt32(data, (int)offset + 4); ;
                UInt32 datFileSize = BitConverter.ToUInt32(data, (int)offset + 8);
                UInt32 filePathIndex = BitConverter.ToUInt32(data, (int)offset + 12);

                string filePath = filePaths[filePathIndex];
                RafFile rafFile = new RafFile(filePath, datFileOffset, datFileSize, archive);
                files.Add(rafFile);
            }

            archive.SetFiles(new ReadOnlyCollection<RafFile>(files));

            return archive;
        }

        public void SetRafFileData(RafFile rafFile)
        {
            if (rafFile.Data == null)
            {
                // Load the file data from the .dat file
                using (FileStream stream = new FileStream(rafFile.RafArchive.DatPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                {
                    byte[] buffer = new byte[rafFile.FileSize];
                    stream.Seek(rafFile.FileOffset, SeekOrigin.Begin);
                    stream.Read(buffer, 0, (int)rafFile.FileSize);

                    byte[] data;
                    try
                    {
                        // Try to decompress the file
                        data = ZlibStream.UncompressBuffer(buffer);
                    }
                    catch
                    {
                        // If that fails it's probably not compressed
                        data = buffer;
                    }

                    rafFile.SetData(data);
                }
            }
        }

        private T[] SubArray<T>(T[] data, int index, int length)
        {
            T[] result = new T[length];
            Array.Copy(data, index, result, 0, length);
            return result;
        }
    }
}
